/*
 * @(#)SourceForgeNavigator.java   09/10/26
 *
 * Copyright (c) 2009 HKUST. All rights reserved.
 *
 */

package org.ust.downloader;

//~--- non-JDK imports --------------------------------------------------------

import org.eclipse.swt.SWT;
import org.eclipse.swt.browser.Browser;
import org.eclipse.swt.browser.ProgressAdapter;
import org.eclipse.swt.browser.ProgressEvent;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

//~--- JDK imports ------------------------------------------------------------

import java.io.IOException;

import java.net.MalformedURLException;

import java.util.Date;
import java.util.Iterator;
import java.util.Timer;
import java.util.TimerTask;
import java.util.Vector;

/**
 * Class description SourceForgeNavigator surfs sourceforge automatically and
 * finds the latest Jar files or Zip files.
 * 
 * @version 1.0, 09/10/26
 * @author Nelson LAU
 */
public class Navigator
{
	private BrowseInstructions	instructions;
	private String				nextUpdatedPageLink;
	private Vector < String >	projectLinksVector;

	/**
	 *
	 */
	public Navigator ( )
	{
		nextUpdatedPageLink = BrowseInstructions.NEXT_LINK_DEFAULT_VALUE;
	}

	/**
	 * 
	 * 
	 * @param instructions
	 */
	public Navigator ( BrowseInstructions instructions )
	{
		setInstructions ( instructions );
		nextUpdatedPageLink = BrowseInstructions.NEXT_LINK_DEFAULT_VALUE;
	}

	/**
	 * Method description Predict the final download link of a project file
	 * using the specified file link.
	 * 
	 * @param fileLink
	 * 
	 * @return a link for downloading the specified file
	 */
	private String predictDownloadLink ( String fileLink )
	{
		return instructions.predictDownloadLink ( fileLink );
	}

	/**
	 * Method description Analyze string comprising the project links from
	 * downloader.SourceForgeNavigator.visitProjectsList(Browser). Then split
	 * the string into several strings each representing one project link.
	 * 
	 * @param projectLinks
	 * 
	 * @return a vector containing project homepage links
	 */
	private Vector < String > analyzeProjectLinks ( String projectLinks )
	{
		return instructions.analyzeProjectLinks ( projectLinks );
	}

	/**
	 * Method description Retrieve the latest file link in the form of string
	 * subject to the specified browser is in the page manifesting project files
	 * of different version.
	 * 
	 * @param browser
	 * 
	 * @return a link to retrieve the latest file
	 */
	private String analyzeNewestFile ( Browser browser )
	{
		return ( String ) browser
				.evaluate ( instructions.analyzeNewestFile ( ) );
	}

	/**
	 * Method description Visit the page manifesting various project links which
	 * constitute a final string to be returned.
	 * 
	 * @param browser
	 * 
	 * @return a string comprising a number of project links, separated by '\n'
	 */
	private String analyzeProjectsList ( Browser browser )
	{
		return ( String ) browser.evaluate ( instructions
				.analyzeProjectsList ( ) );
	}

	/**
	 * Retrieve the link to the page with project files to download, subject to
	 * the browser surfing the project homepage.
	 * 
	 * @param browser
	 * 
	 * @return a link to file page of each project
	 */
	private String analyzeFilePage ( Browser browser )
	{
		return ( String ) browser.evaluate ( instructions.analyzeFilePage ( ) );
	}

	/**
	 * Navigate individual project homepage and trigger download process.
	 * 
	 * @param targetUrl
	 * @param projectListBrowser
	 * @param shell
	 * @param display
	 * @param lockManager
	 * 
	 * @throws Exception
	 */
	private void navigate ( final String targetUrl ,
			final Browser projectListBrowser , final Shell shell ,
			final Display display , final LockManager lockManager )
			throws Exception
	{
		final ProjectListNavigationLock projectListBrowserLock = new ProjectListNavigationLock ( );

		lockManager.addLock ( projectListBrowserLock );
		initializeLock ( projectListBrowserLock , "Visiting List Page:\t"
				+ targetUrl );
		projectListBrowser.addProgressListener ( new ProgressAdapter ( )
		{
			@ Override
			public void changed ( ProgressEvent event )
			{
				instructions.duringNavigateProjectList ( targetUrl ,
						projectListBrowser , shell , display );

				synchronized ( projectListBrowserLock )
				{
					if ( projectListBrowserLock.isAbort ( ) )
					{
						System.out
								.println ( "Stopping browser surfing project list page..." );
						projectListBrowser.stop ( );
					}
				}
			}

			@ Override
			public void completed ( ProgressEvent projectLinksEvent )
			{
				/*
				 * Project List
				 */
				try
				{
					instructions.postNavigateProjectList ( targetUrl ,
							projectListBrowser , shell , display );

					boolean hasProjectListBrowserAborted = false;

					synchronized ( projectListBrowserLock )
					{
						if ( projectListBrowserLock.isAbort ( ) == false )
						{
							projectListBrowserLock.setFinished ( true );
						}
						else
						{
							hasProjectListBrowserAborted = true;
						}
					}

					String projectLinks = analyzeProjectsList ( projectListBrowser );

					projectLinksVector = analyzeProjectLinks ( projectLinks );

					Iterator < String > linksIterator = null;

					// if ( projectLinksVector.size ( ) > 0 )
					// {
					linksIterator = projectLinksVector.iterator ( );
					// }

					if ( hasProjectListBrowserAborted )
					{
						projectListBrowser.removeProgressListener ( this );

						throw new CannotReachPage (
								"Page navigation to Project List Page "
										+ targetUrl
										+ " exceeded "
										+ projectListBrowserLock
												.getMaxPausePeriodInSecond ( )
										+ "." );
					}

					// if ( linksIterator == null )
					// {
					// throw new Exception ( "Cannot analyse project links" );
					// }

					while ( linksIterator.hasNext ( ) )
					{
						final String eachProjectLink = linksIterator.next ( );
						final Shell fileShell = new Shell ( display );

						fileShell.setBounds ( 14 , 14 , 500 , 500 );
						fileShell.setLayout ( new FillLayout ( ) );

						final Browser fileBrowser = new Browser ( fileShell ,
								SWT.MOZILLA );

						fileBrowser.setVisible ( false );

						final HomepageNavigationLock homepageBrowserLock = new HomepageNavigationLock ( );

						lockManager.addLock ( homepageBrowserLock );
						fileBrowser
								.addProgressListener ( new ProgressAdapter ( )
								{
									@ Override
									public void changed ( ProgressEvent event )
									{
										instructions
												.duringNavigateProjectHomepage (
														eachProjectLink ,
														fileBrowser ,
														fileShell , display );

										synchronized ( homepageBrowserLock )
										{
											if ( homepageBrowserLock.isAbort ( ) )
											{
												System.out
														.println ( "Stopping browser from surfing homepage..." );
												fileBrowser.stop ( );
											}
										}
									}

									@ Override
									public void completed (
											ProgressEvent projectLinksEvent )
									{
										/*
										 * Each Project Homepage
										 */
										instructions
												.postNavigateProjectHomepage (
														eachProjectLink ,
														fileBrowser ,
														fileShell , display );

										final String fileLink = analyzeFilePage ( fileBrowser );
										boolean hasHomepageBrowserAborted = false;

										synchronized ( homepageBrowserLock )
										{
											if ( homepageBrowserLock.isAbort ( ) == false )
											{
												homepageBrowserLock
														.setFinished ( true );
											}
											else
											{
												hasHomepageBrowserAborted = true;
											}
										}

										if ( hasHomepageBrowserAborted )
										{
											try
											{
												fileBrowser
														.removeProgressListener ( this );

												throw new CannotReachPage (
														"Page navigation to Project Homepage "
																+ eachProjectLink
																+ " exceeded "
																+ homepageBrowserLock
																		.getMaxPausePeriodInSecond ( )
																+ " seconds." );
											}
											catch ( CannotReachPage e )
											{
												System.out.println ( e
														.getMessage ( ) );
											}
										}
										else
										{
											final FilePageNavigationLock fileBrowserLock = new FilePageNavigationLock ( );

											lockManager
													.addLock ( fileBrowserLock );
											fileBrowser
													.removeProgressListener ( this );
											fileBrowser
													.addProgressListener ( new ProgressAdapter ( )
													{
														@ Override
														public void changed (
																ProgressEvent event )
														{
															instructions
																	.duringNavigateProjectFilePage (
																			eachProjectLink ,
																			fileBrowser ,
																			fileShell ,
																			display );

															synchronized ( fileBrowserLock )
															{
																if ( fileBrowserLock
																		.isAbort ( ) )
																{
																	System.out
																			.println ( "Stopping browser from surfing file page: "
																					+ fileLink );
																	fileBrowser
																			.stop ( );
																}
															}
														}

														@ Override
														public void completed (
																ProgressEvent fileLinksEvent )
														{
															/*
															 * File Page
															 */
															try
															{
																instructions
																		.postNavigateProjectFilePage (
																				eachProjectLink ,
																				fileBrowser ,
																				fileShell ,
																				display );

																String downloadLatestFile = analyzeNewestFile ( fileBrowser );
																boolean hasFileBrowserAborted = false;

																synchronized ( fileBrowserLock )
																{
																	if ( fileBrowserLock
																			.isAbort ( ) )
																	{
																		hasFileBrowserAborted = true;
																	}
																	else
																	{
																		fileBrowserLock
																				.setFinished ( true );
																	}
																}

																if ( hasFileBrowserAborted )
																{
																	fileBrowser
																			.removeProgressListener ( this );

																	throw new CannotReachPage (
																			"Page navigation to File Page "
																					+ fileLink
																					+ " exceed "
																					+ fileBrowserLock
																							.getMaxPausePeriodInSecond ( )
																					+ " seconds." );
																}
																else if ( downloadLatestFile == null )
																{
																	throw new CannotReachPage (
																			"File extension for "
																					+ fileLink
																					+ " is not suitable for downloading." );
																}
																else
																{
																	final DownloadLock abortDownloadLock = new DownloadLock ( );

																	lockManager
																			.addLock ( abortDownloadLock );
																	initializeLock (
																			abortDownloadLock ,
																			"Downloading Page:\t"
																					+ downloadLatestFile );
																	downloadFile (
																			downloadLatestFile ,
																			abortDownloadLock );
																}
															}
															catch ( CannotCreateFolderException e )
															{
																System.out
																		.println ( e
																				.getMessage ( ) );
															}
															catch ( CannotReachPage e )
															{
																System.out
																		.println ( e
																				.getMessage ( ) );
															}
															catch ( MalformedURLException e )
															{
																e
																		.printStackTrace ( );
															}
															catch ( IOException e )
															{
																e
																		.printStackTrace ( );
															}
														}
													} );
											initializeLock ( fileBrowserLock ,
													"Visiting File Page:\t"
															+ fileLink );
											instructions
													.preNavigateProjectFilePage (
															eachProjectLink ,
															fileBrowser ,
															fileShell , display );
											fileBrowser.setUrl ( fileLink );
										}
									}
								} );
						initializeLock ( homepageBrowserLock ,
								"Visiting Project Homepage:\t"
										+ eachProjectLink );
						instructions.preNavigateProjectHomepage (
								eachProjectLink , fileBrowser , fileShell ,
								display );
						fileBrowser.setUrl ( eachProjectLink );
					}

					synchronized ( nextUpdatedPageLink )
					{
						nextUpdatedPageLink = lookForNextPageLink (
								projectListBrowser , display , lockManager );
					}
				}
				catch ( CannotReachPage e )
				{
					synchronized ( nextUpdatedPageLink )
					{
						nextUpdatedPageLink = null;
					}

					System.out.println ( e.getMessage ( ) );
				}
				catch ( Exception e )
				{
					synchronized ( nextUpdatedPageLink )
					{
						nextUpdatedPageLink = null;
					}

					e.printStackTrace ( );
				}
			}
		} );
		instructions.preNavigateProjectList ( targetUrl , projectListBrowser ,
				shell , display );
		projectListBrowser.setUrl ( targetUrl );
	}

	/**
	 * 
	 * 
	 * @param projectListBrowser
	 * @param display
	 * @param lockManager
	 * 
	 * @return
	 * 
	 * @throws Exception
	 */
	private String lookForNextPageLink ( final Browser projectListBrowser ,
			final Display display , final LockManager lockManager )
			throws Exception
	{
		return ( String ) projectListBrowser.evaluate ( instructions
				.lookForNextPageLink ( ) );
	}

	/**
	 * It visits every single page comprising a list of project home pages.
	 * 
	 * @param targetUrl
	 */
	private void navigateProjectLists ( String targetUrl )
	{
		LockManager lockManager = new LockManager ( );
		Display display = new Display ( );
		Shell shell = new Shell ( display );

		try
		{
			shell.setBounds ( 10 , 10 , 500 , 500 );
			shell.setLayout ( new FillLayout ( ) );
			shell.setVisible ( false );

			final Browser browser = new Browser ( shell , SWT.MOZILLA );

			browser.setVisible ( false );
			navigate ( targetUrl , browser , shell , display , lockManager );
			shell.open ( );

			while ( ! shell.isDisposed ( ) )
			{
				// if (lockManager.isAllLocksFinished()
				if ( lockManager.noRunningLocks ( ) )
				// || lockManager.noRunningHomepageLocks ( )
				// || lockManager.noRunningDownloadLocks ( )
				// || lockManager.noRunningFilePageLocks ( ) )
				{
					shell.dispose ( );
				}

				if ( ! display.readAndDispatch ( ) )
				{
					display.sleep ( );
				}
			}
		}
		catch ( Exception e )
		{
			e.printStackTrace ( );
		}

		display.dispose ( );
	}

	/**
	 * 
	 * 
	 * @param lock
	 * @param msg
	 */
	private void initializeLock ( final NavigationLock lock , final String msg )
	{
		Timer abortTimer = new Timer ( );
		Date time = new Date ( );
		TimerTask abortTask = new TimerTask ( )
		{
			private int	count	= 0;

			@ Override
			public void run ( )
			{
				try
				{
					synchronized ( lock )
					{
						lock.setAbort ( false );
					}

					while ( count < lock.getMaxPausePeriodInSecond ( ) )
					{
						synchronized ( lock )
						{
							if ( lock.isFinished ( ) )
							{
								return;
							}
						}

						System.out.println ( msg + "\t" + count );
						Thread.sleep ( 1000 );
						count++ ;
					}

					synchronized ( lock )
					{
						System.out.println ( "Aborting process\t" + msg );
						lock.setAbort ( true );
					}
				}
				catch ( InterruptedException e )
				{
					System.out.println ( e.getMessage ( ) );
				}
			}
		};

		abortTimer.schedule ( abortTask , time );
	}

	/**
	 * 
	 * 
	 * @param downloadLatestFile
	 * @param shouldAbortLock
	 * 
	 * @throws CannotCreateFolderException
	 * @throws IOException
	 * @throws MalformedURLException
	 */
	private void downloadFile ( String downloadLatestFile ,
			NavigationLock shouldAbortLock )
			throws CannotCreateFolderException , MalformedURLException ,
			IOException
	{
		Downloader d = new Downloader ( this
				.predictDownloadLink ( downloadLatestFile ) , instructions
				.getDownloadFolderName ( ) );

		instructions.preDownloadFile ( downloadLatestFile );
		d.beginDownload ( shouldAbortLock );
		instructions.postDownloadFile ( downloadLatestFile );
	}

	/**
	 * Begin the whole process.
	 * 
	 */
	public void begin ( )
	{
		try
		{
			if ( instructions == null )
			{
				throw new InstructionNotFoundException (
						"Please specify instructions Navigator follows." );
			}

			instructions.preBeginAction ( );
			navigateProjectLists ( instructions.getInitialProjectListUrl ( ) );

			String nextPageLink = null;

			do
			{
				boolean isReadyForNextPage = false;

				synchronized ( nextUpdatedPageLink )
				{
					// last page
					if ( nextUpdatedPageLink == null )
					{
						break;
					}

					// > 1st page && < last page
					if ( nextPageLink != null )
					{
						if ( ! nextUpdatedPageLink.equals ( nextPageLink )
								&& ! nextUpdatedPageLink
										.equals ( BrowseInstructions.NEXT_LINK_DEFAULT_VALUE ) )
						{
							isReadyForNextPage = true;
							nextPageLink = nextUpdatedPageLink;
						}
					}

					// 1st page
					else
					{
						if ( ! nextUpdatedPageLink
								.equals ( BrowseInstructions.NEXT_LINK_DEFAULT_VALUE ) )
						{
							isReadyForNextPage = true;
							nextPageLink = nextUpdatedPageLink;
						}
					}
				}

				if ( isReadyForNextPage )
				{
					navigateProjectLists ( nextPageLink );
				}
			}
			while ( true );

			instructions.postBeginAction ( );
		}
		catch ( InstructionNotFoundException e )
		{
			e.printStackTrace ( );
		}
	}

	/**
	 * 
	 * 
	 * @param instructions
	 */
	public void setInstructions ( BrowseInstructions instructions )
	{
		this.instructions = instructions;
	}

	/**
	 * 
	 * 
	 * @return
	 */
	public BrowseInstructions getInstructions ( )
	{
		return instructions;
	}

	/**
	 * 
	 * @param args
	 */
	// public static void main ( String [ ] args )
	// {
	// BrowseInstructions instructions = new SourceForgeInstructions ( "APIs" );
	// Navigator sfNavigator = new Navigator ( instructions );
	//
	// sfNavigator.begin ( );
	// }
}
